package com.xhs.minio.core.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xhs.minio.core.enums.FileBucketAuthEnum;
import com.xhs.minio.core.exception.CommonException;
import com.xhs.minio.core.props.MinioPropertiesParam;
import io.minio.*;
import io.minio.http.Method;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;

/**
 * @desc: MINIO文件工具类
 * 参考文档：<a href="http://docs.minio.org.cn/docs/master/java-client-quickstart-guide">参考文档</a>
 * @projectName: spring-boot-minio
 * @author: xhs
 * @date: 2025-08-16 周六 17:15
 */
@Slf4j
@Component
public class MinIoUtil {


    @Resource
    private MinioClient client;
    @Resource
    private MinioPropertiesParam minioProps;


    /**
     * 销毁操作的客户端
     */
    public void destroyClient() {
    }

    /**
     * 获取默认存储桶名称
     **/
    public String getDefaultBucketName() {
        return minioProps.getBucketName();
    }

    /**
     * 查询存储桶是否存在
     * 例如：传入参数 bucket-1250000000，返回true代表存在此桶
     *
     * @param bucketName 桶名称
     */
    public boolean doesBucketExist(String bucketName) {
        try {
            BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build();
            return client.bucketExists(bucketExistsArgs);
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 桶名称
     */
    public void makeBucket(String bucketName) {
        try {
            client.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }
    }

    /**
     * 删除存储桶
     *
     * @param bucketName 桶名称
     */
    public boolean removeBucket(String bucketName) {
        try {
            client.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
            return true;
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }
    }

    /**
     * 设置预定义策略
     * 预定义策略如公有读、公有读写、私有读
     *
     * @param bucketName         桶名称
     * @param fileBucketAuthEnum 存储桶权限
     */
    public void setBucketAcl(String bucketName, FileBucketAuthEnum fileBucketAuthEnum) {
        setFileAcl(bucketName, "*", fileBucketAuthEnum);
    }

    /**
     * 判断是否存在文件
     *
     * @param bucketName 桶名称
     * @param key        唯一标示id，例如a.txt, doc/a.txt
     */
    public boolean isExistingFile(String bucketName, String key) {
        try {
            GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucketName).object(key).build();
            InputStream object = client.getObject(getObjectArgs);
            return !ObjectUtil.isEmpty(object);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 存储文件，不返回地址
     *
     * @param bucketName 桶名称
     * @param key        唯一标示id，例如a.txt, doc/a.txt
     * @param file       文件
     */
    public void storageFile(String bucketName, String key, File file) {
        BufferedInputStream inputStream;
        try {
            inputStream = FileUtil.getInputStream(file);
        } catch (IORuntimeException e) {
            throw new CommonException("获取文件流异常，名称是：{}", file.getName());
        }
        storageFile(bucketName, key, inputStream);
    }

    /**
     * 存储文件，不返回地址
     *
     * @param bucketName    桶名称
     * @param key           唯一标示id，例如a.txt, doc/a.txt
     * @param multipartFile 文件
     */
    public void storageFile(String bucketName, String key, MultipartFile multipartFile) {
        InputStream inputStream;
        try {
            inputStream = multipartFile.getInputStream();
        } catch (IOException e) {
            throw new CommonException("获取文件流异常，名称是：{}", multipartFile.getName());
        }
        storageFile(bucketName, key, inputStream);
    }

    /**
     * 存储文件，不返回地址
     *
     * @param bucketName 桶名称
     * @param key        唯一标示id，例如a.txt, doc/a.txt
     * @param bytes      文件字节数组
     */
    public void storageFile(String bucketName, String key, byte[] bytes) {
        ByteArrayInputStream byteArrayInputStream = null;
        try {
            byteArrayInputStream = new ByteArrayInputStream(bytes);
            PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucketName).object(key)
                    .contentType(getFileContentType(key)).stream(byteArrayInputStream, bytes.length, -1).build();
            client.putObject(putObjectArgs);
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        } finally {
            IoUtil.close(byteArrayInputStream);
        }
    }

    /**
     * 存储文件，不返回地址
     *
     * @param bucketName  桶名称
     * @param key         唯一标示id，例如a.txt, doc/a.txt
     * @param inputStream 文件流
     */
    public void storageFile(String bucketName, String key, InputStream inputStream) {
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucketName).object(key)
                    .contentType(getFileContentType(key)).stream(inputStream, inputStream.available(), -1).build();
            client.putObject(putObjectArgs);
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        } finally {
            IoUtil.close(inputStream);
        }
    }

    /**
     * 存储文件，返回外网地址
     *
     * @param bucketName 桶名称
     * @param key        唯一标示id，例如a.txt, doc/a.txt
     * @param file       文件
     */
    public String storageFileWithReturnUrl(String bucketName, String key, File file) {
        storageFile(bucketName, key, file);
        setFileAcl(bucketName, key, FileBucketAuthEnum.PUBLIC_READ);
        return getFileAuthUrl(bucketName, key);
    }

    /**
     * 存储文件，返回外网地址
     *
     * @param bucketName    桶名称
     * @param key           唯一标示id，例如a.txt, doc/a.txt
     * @param multipartFile 文件
     */
    public String storageFileWithReturnUrl(String bucketName, String key, MultipartFile multipartFile) {
        storageFile(bucketName, key, multipartFile);
        setFileAcl(bucketName, key, FileBucketAuthEnum.PUBLIC_READ);
        return getFileAuthUrl(bucketName, key);
    }

    /**
     * 存储文件，返回外网地址
     *
     * @param bucketName 桶名称
     * @param key        唯一标示id，例如a.txt, doc/a.txt
     * @param bytes      文件字节数组
     */
    public String storageFileWithReturnUrl(String bucketName, String key, byte[] bytes) {
        storageFile(bucketName, key, bytes);
        setFileAcl(bucketName, key, FileBucketAuthEnum.PUBLIC_READ);
        return getFileAuthUrl(bucketName, key);
    }

    /**
     * 存储文件，返回外网地址
     *
     * @param bucketName  桶名称
     * @param key         唯一标示id，例如a.txt, doc/a.txt
     * @param inputStream 文件流
     */
    public String storageFileWithReturnUrl(String bucketName, String key, InputStream inputStream) {
        storageFile(bucketName, key, inputStream);
        setFileAcl(bucketName, key, FileBucketAuthEnum.PUBLIC_READ);
        return getFileAuthUrl(bucketName, key);
    }

    /**
     * 获取某个bucket下的文件字节
     *
     * @param bucketName 桶名称
     * @param key        唯一标示id，例如a.txt, doc/a.txt
     */
    public byte[] getFileBytes(String bucketName, String key) {
        try {
            GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucketName).object(key).build();
            InputStream inputStream = client.getObject(getObjectArgs);
            return IoUtil.readBytes(inputStream);
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }
    }

    /**
     * 设置文件访问权限管理
     *
     * @param bucketName         桶名称
     * @param key                唯一标示id，例如a.txt, doc/a.txt
     * @param fileBucketAuthEnum 文件权限
     */
    public void setFileAcl(String bucketName, String key, FileBucketAuthEnum fileBucketAuthEnum) {
        try {
            JSONObject configObject = JSONUtil.createObj().set("Version", "2012-10-17");
            JSONArray statementArray = JSONUtil.createArray();
            JSONArray actionArray = JSONUtil.createArray();
            if (fileBucketAuthEnum.equals(FileBucketAuthEnum.PUBLIC_READ)) {
                actionArray.put("s3:GetObject");
            }
            if (fileBucketAuthEnum.equals(FileBucketAuthEnum.PUBLIC_READ_WRITE)) {
                actionArray.put("s3:GetObject");
                actionArray.put("s3:PutObject");
            }
            JSONObject statementObject = JSONUtil.createObj();
            statementObject.set("Effect", "Allow").set("Principal", JSONUtil.createObj().set("AWS", JSONUtil.createArray().put("*")))
                    .set("Action", actionArray).set("Resource", JSONUtil.createArray().put("arn:aws:s3:::" + bucketName + "/*"));
            statementArray.put(statementObject);
            configObject.set("Statement", statementArray);
            String config = JSONUtil.toJsonStr(configObject);
            SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder().bucket(bucketName).config(config).build();
            client.setBucketPolicy(setBucketPolicyArgs);
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }
    }

    /**
     * 拷贝文件
     *
     * @param originBucketName 源文件桶
     * @param originFileKey    源文件名称
     * @param newBucketName    新文件桶
     * @param newFileKey       新文件名称
     */
    public void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) {
        try {
            CopySource copySource = CopySource.builder().bucket(originBucketName).object(originFileKey).build();
            CopyObjectArgs copyObjectArgs = CopyObjectArgs.builder().source(copySource).bucket(newBucketName).object(newFileKey).build();
            client.copyObject(copyObjectArgs);
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }
    }

    /**
     * 获取文件的下载地址（带鉴权和有效时间的），生成外网地址
     *
     * @param bucketName    文件桶
     * @param key           唯一标示id，例如a.txt, doc/a.txt
     * @param timeoutMillis 时效
     */
    public String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) {
        try {
            GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder().bucket(bucketName)
                    .object(key).method(Method.GET).expiry(timeoutMillis.intValue()).build();
            return client.getPresignedObjectUrl(getPresignedObjectUrlArgs);
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }
    }

    /**
     * 获取文件的下载地址（永久的，文件必须为公有读），生成外网地址
     *
     * @param bucketName 文件桶
     * @param key        唯一标示id，例如a.txt, doc/a.txt
     */
    public String getFileAuthUrl(String bucketName, String key) {
        try {
            return minioProps.getDomain() + StrUtil.SLASH + bucketName + StrUtil.SLASH + key;
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }
    }

    /**
     * 删除文件
     *
     * @param bucketName 文件桶
     * @param key        唯一标示id，例如a.txt, doc/a.txt
     */
    public void deleteFile(String bucketName, String key) {
        try {
            RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucketName).object(key).build();
            client.removeObject(removeObjectArgs);
        } catch (Exception e) {
            throw new CommonException(e.getMessage());
        }
    }

    /**
     * 根据文件名获取ContentType
     **/
    private String getFileContentType(String key) {
        // 根据文件名获取contentType
        String contentType = "application/octet-stream";
        if (key.contains(".")) {
//            contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(key);
            contentType = MediaTypeFactory.getMediaType(key)
                    .orElse(MediaType.APPLICATION_OCTET_STREAM)
                    .toString();
        }
        return contentType;
    }
}
